using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

namespace BardsTaleViewer {
  public class Picture_Amiga : IPicture {
    public Bitmap _bitmap;
    public IAnims _anims;
    public static byte [] _dumpBard3;

    static Picture_Amiga () {
      _dumpBard3 = Properties.Resources.bard3.Extract (0);
    }

    public Picture_Amiga (Bitmap bitmap, IAnims anims) {
      _bitmap = bitmap;
      _anims = anims;
    }

    public static Picture_Amiga Load (string filename, byte [] dump, int pictureIndex, int bardVersion) {
      if ((bardVersion == 1) || (bardVersion == 2))
        return (LoadBard_1_2 (dump, pictureIndex, bardVersion));
      else if (bardVersion == 3)
        return (LoadBard_3 (dump, pictureIndex));
      else if (bardVersion == 4)
        return (LoadBard_4 (filename, pictureIndex));
      else
        return (null);
    }

    public static Picture_Amiga LoadBard_1_2 (byte [] dump, int pictureIndex, int bardVersion) {
      int offset = pictureIndex * 4;
      offset = (int) dump.ReadLong (offset);
      int offsetNext = (int) dump.ReadLong (pictureIndex * 4 + 4);
      int srcRead = offset;
      int sizeSrcComputed = offsetNext - offset;
      int sizeDst = (int) dump.ReadLong (ref offset);
      int unk = (int) dump.ReadLong (ref offset);

      Huffman huffman = new Huffman (dump, ref offset, Computer.Amiga);

      //CheckTree (huffman, Properties.Resources.Tree_0x30, 0, 0x3514C, Computer.Amiga);

      byte [] a = huffman.Decode_Amiga (dump, ref offset, 32);
      Palette palette = new Palette (a, 0, 16, Palette.LookupTypeEnum.Amiga);
      //a.WriteToFile ("a.dmp");
      int offsetSrc = 0;
      byte [] b = huffman.Decode_Amiga (dump, ref offset, sizeDst - 32);
      //b.WriteToFile ("b.dmp");
      Bitmap bitmap = null;
      byte [] basePicture = null;

      srcRead = offset - srcRead;
      if (bardVersion == 1) {
        byte [] bb = RLE.Decode (b, ref offsetSrc);
        //bb.WriteToFile ("bb.dmp");
        if (bb.Length != (112 * 88 / 2))
          throw new Exception ();
        basePicture = bb;
        if ((offsetSrc % 2) == 1)
          offsetSrc++;
      }
      else if (bardVersion == 2) {
        byte [] b1 = Uncrypt1 (b);
        //b1.WriteToFile ("b1.dmp");
        Uncrypt2 (b1);
        //b1.WriteToFile ("b2.dmp");
        basePicture = b1;
        offsetSrc = 1232 * 4;
      }
      //else if (bardVersion == 3) {
      //  byte [] b1 = Uncrypt1 (b);
      //  b1.WriteToFile ("b1.dmp");
      //  Uncrypt2 (b1);
      //  b1.WriteToFile ("b2.dmp");
      //  basePicture = b1;
      //  offsetSrc = 1232 * 4;
      //}
      bitmap = SpriteReader.CreateBitmap (basePicture, 0, 112, 88, 4, SpriteReader.ModesEnum.Planar, -1, palette, 0, SpriteReader.MasksEnum.Opaque);
      //bitmap.Save (string.Format ("Picture {0} Amiga.png", pictureIndex.ToString ().PadLeft (2, '0')), ImageFormat.Png);

      byte [] c = b.Extract (offsetSrc);
      //Console.WriteLine (c.Dump (0, c.Length, 0, 1, 16, false));
      //c.WriteToFile ("Amiga anim.dmp");
      //if ((sizeSrcComputed != srcRead) &&
      //  (sizeSrcComputed != (srcRead - 1)) &&
      //  (sizeSrcComputed != (srcRead - 2)))
      //  throw new Exception ();

      int offsetAnim = 0;
      IAnims anims = null;
      if (bardVersion == 1)
        anims = new Anims_Amiga_Bard1 (c, ref offsetAnim, pictureIndex);
      else if (bardVersion == 2)
        anims = new Anims_Amiga_Bard2 (c, ref offsetAnim, pictureIndex);
      else if (bardVersion == 3)
        anims = new Anims_Amiga_Bard2 (c, ref offsetAnim, pictureIndex);

      anims.CreateBitmaps (bitmap, palette, basePicture);
      return (new Picture_Amiga (bitmap, anims));
    }

    public static Picture_Amiga LoadBard_3 (byte [] dump, int pictureIndex) {
      int offset = pictureIndex * 4;
      int offsetNext = (pictureIndex + 1) * 4;
      offset = (int) dump.ReadLong (offset);
      offsetNext = (int) dump.ReadLong (offsetNext);
      int offsetStart = offset;
      int compressedSize = offsetNext - offset;
      int uncompressedSize = (int) dump.ReadLong (ref offset);

      byte [] unpacked = Unpack_Bard3 (dump, ref offset, uncompressedSize);
      if ((offsetStart + compressedSize) != (offset - 1))
        throw new Exception ();
      //unpacked.WriteToFile ("test unpacked.dmp");
      //Console.WriteLine (unpacked.Dump (0, unpacked.Length, 0x77C20, 2, 8, true));
      UncryptXor_Bard3 (ref unpacked);
      //unpacked.WriteToFile ("test uncrypt xor.dmp");

      Palette palette = GetPalette_Bard3 (pictureIndex);
      Bitmap [] bitmaps = new Bitmap [4];
      Anims_Amiga_Bard3 anims = new Anims_Amiga_Bard3 ();
      Anim_Amiga_Bard3 anim = new Anim_Amiga_Bard3 ();

      for (int i = 0; i < 4; i++) {
        Bitmap bitmap = SpriteReader.CreateBitmap (unpacked, i * 112 * 88 / 2, 112, 88, 4, SpriteReader.ModesEnum.Chunky, -1, palette, 0, SpriteReader.MasksEnum.Opaque);
        //bitmap.Save ("test.png", ImageFormat.Png);
        bitmaps [i] = bitmap;
        AnimFrame_Amiga_Bard3 frame = new AnimFrame_Amiga_Bard3 (0, i, 0, bitmap);
        anim.AddFrame (frame);
      }
      anims.AddAnim (anim);

      return (new Picture_Amiga (bitmaps [0], anims));
    }

    public static Palette GetPalette_Bard3 (int pictureIndex) {
      pictureIndex = GetPictureIndexGame (pictureIndex);
      if (pictureIndex >= 88)
        throw new Exception ();
      //int paletteIndex = _dumpBard3.ReadByte (0x20988 + pictureIndex);
      int paletteIndex = _dumpBard3.ReadByte (0x25A98 + pictureIndex);
      //int paletteOffset = 0x20868 + (paletteIndex << 5);
      int paletteOffset = 0x25978 + (paletteIndex * 16 * 2);
      Palette palette = new Palette (_dumpBard3, paletteOffset, 16, Palette.LookupTypeEnum.Amiga);
      return (palette);
    }

    public static int GetPictureIndexGame (int pictureIndex) {
      for (int i = 0; i < 88; i++)
        if (pictureIndex == _dumpBard3.ReadByte (0x2589E + i))
          return (i);
      throw new Exception ();
    }

    public static void DumpTab (byte [] tab, int index) {
      int address = 0;
      if (index == 1)
        address = 0x48d26;
      else if (index == 2)
        address = 0x49d62;
      else if (index == 3)
        address = 0x4a24a;
      else if (index == 4)
        address = 0x4a9a4;

      Console.WriteLine (tab.Dump (0, tab.Length, address, 2, 8, true));
    }

    public static byte [] Unpack_Bard3 (byte [] src, ref int offsetSrc, int uncompressedSize) {
      byte [] dst = new byte [19712];

      byte [] tab1 = new byte [4036 + 60];
      byte [] tab2 = new byte [628 * 2];
      byte [] tab3 = new byte [627 * 2 + 314 * 2];
      byte [] tab4 = new byte [627 * 2];

      Unpack_Bard3_Init (ref tab2, ref tab3, ref tab4);

      //DumpTab (tab4, 4);

      for (int d5 = 4035; d5 >= 0; d5--)
        tab1.WriteByte (d5, 32);

      int offsetTab1 = 4036;
      int d3 = 0;
      int d7 = 0;
      // Loop1
      int offsetDst = 0;
      for (; offsetDst < uncompressedSize; ) {
        int d6 = tab4.ReadWord (0x4E4);
        // Loop1_1
        for (; d6 < 627; ) {
          // Loop1_1_1
          for (; d3 <= 8; ) {
            int d2 = src.ReadByte (ref offsetSrc);
            d7 |= (d2 << (8 - d3)) & 0xFFFF;
            d3 += 8;
          }
          // Break1_1_1
          if (((short) d7) < 0)
            d6++;
          d7 = (d7 << 1) & 0xFFFF;
          d3--;
          d6 = tab4.ReadWord (d6 * 2);
        }
        // Break1_1 : 22BA6
        //if (offsetDst >= 0x65) {
        //  Console.WriteLine ();
        //  Console.WriteLine (dst.Dump (0, 0x100, 0x77C20, 2, 8, true));
        //  Console.WriteLine (tab1.Dump (0, 0x100, 0x48D26, 2, 8, true));
        //}

        d6 -= 627;
        Unpack_Bard3_HelperA (d6, ref tab2, ref tab3, ref tab4);

        //if (offsetDst == 0x5D)
        //  Console.WriteLine (dst.Dump (0, 0x100, 0x77C20, 2, 8, true));
        if (d6 < 256) {
          dst.WriteByte (offsetDst++, d6);
          tab1.WriteByte (offsetTab1++, d6);
          offsetTab1 &= 0xFFF;
        }
        else {
          // Loop1_2
          for (; d3 <= 8; ) {
            int d2 = src.ReadByte (ref offsetSrc);
            d7 |= (d2 << (8 - d3)) & 0xFFFF;
            d3 += 8;
          }
          // Break1_2
          int var_E = d7 >> 8;
          d7 = (d7 << 8) & 0xFFFF;
          d3 -= 8;
          int var_12 = _hard1.ReadByte (var_E) << 6;
          int var_10 = _hard2.ReadByte (var_E);
          var_10 -= 2;

          // Loop1_3 : OK
          for (; ; ) {
            var_10--;
            if (var_10 == -1)
              break;
            // Loop1_3_1 : OK
            for (; d3 <= 8; ) {
              int d2 = src.ReadByte (ref offsetSrc);
              d7 |= (d2 << (8 - d3)) & 0xFFFF;
              d3 += 8;
            }
            // Break1_3_1 : 22C7E
            var_E = (var_E << 1) & 0xFFFF;
            if (((short) d7) < 0)
              var_E++;
            d7 = (d7 << 1) & 0xFFFF;
            d3--;
          }
          // Break1_3
          int z4_d0 = (var_E & 0x3F) | var_12;
          int d5 = (offsetTab1 - z4_d0 - 1) & 0xFFF;
          // Loop1_4
          int var_4 = d6 - 0xFD;
          for (int j = 0; j < var_4; j++) {
            d6 = tab1.ReadByte ((d5 + j) & 0xFFF);
            dst.WriteByte (offsetDst++, d6);
            tab1.WriteByte (offsetTab1++, d6);
            offsetTab1 &= 0xFFF;
          }
          // Next1_4
        }
        // Next1
      }
      if (offsetDst != dst.Length)
        throw new Exception ();

      return (dst);
    }

    public static void Unpack_Bard3_HelperA (int arg_0, ref byte [] tab2, ref byte [] tab3, ref byte [] tab4) {
      if (tab2.ReadWord (0x4E4) == 0x8000)
        Unpack_Bard3_HelperB (ref tab2, ref tab3, ref tab4);
      int d0 = (arg_0 + 627) * 2;
      int d4 = tab3.ReadWord (d0);
      // Loop
      for (; ; ) {
        int d2 = d4 * 2;
        tab2.WriteWord (d2, tab2.ReadWord (d2) + 1);
        int d7 = tab2.ReadWord (d2);
        int d3 = d4 + 1;
        int d1 = d2 + 2;
        if (d7 > tab2.ReadWord (d1)) {
          for (; ; ) {
            d3++;
            d1 += 2;
            if (d7 <= tab2.ReadWord (d1))
              break;
          }
          d3--;
          d1 -= 2;
          tab2.WriteWord (d2, tab2.ReadWord (d1));
          tab2.WriteWord (d1, d7);
          int d5 = tab4.ReadWord (d2);
          d0 = d5 * 2;
          tab3.WriteWord (d0, d3);
          if (d5 < 627) {
            d0 += 2;
            tab3.WriteWord (d0, d3);
          }
          int d6 = tab4.ReadWord (d1);
          tab4.WriteWord (d1, d5);
          d0 = d6 * 2;
          tab3.WriteWord (d0, d4);
          if (d6 < 627) {
            d0 += 2;
            tab3.WriteWord (d0, d4);
          }
          tab4.WriteWord (d2, d6);
          d4 = d3;
        }
        d2 = d4 * 2;
        d4 = tab3.ReadWord (d2);
        if (d4 == 0)
          break;
      }
    }

    public static void Unpack_Bard3_HelperB (ref byte [] tab2, ref byte [] tab3, ref byte [] tab4) {
      {
        int d4 = 0;
        int d5 = 0;
        // Loop1
        for (; d4 < 627; ) {
          if (tab4.ReadWord (d4 * 2) >= 627) {
            int d0 = d5 * 2;
            int d1 = d4 * 2;
            int d2 = tab2.ReadWord (d1);
            d2 = (d2 + 1) >> 1;
            tab2.WriteWord (d0, d2);
            int d0_ = d4 * 2;
            int d1_ = d5 * 2;
            tab4.WriteWord (d1_, tab4.ReadWord (d0_));
            d5++;
          }
          d4++;
        }
      }

      // Loop2
      {
        int d4 = 0;
        int d5 = 627;
        for (; d5 < 627; ) {
          int d6 = d4 + 1;
          int d0 = d5 * 2;
          int d1 = d4 * 2;
          int d2 = d6 * 2;
          int d3 = tab2.ReadWord (d1) + tab2.ReadWord (d2);
          tab2.WriteWord (d0, d3);
          int var_2 = d3;
          d6 = d5;
          for (; ; ) {
            d6--;
            if (var_2 >= tab2.ReadWord (d6 * 2))
              break;
          }
          d6++;
          int var_4 = (d5 - d6 * 2);
          int offsetDst = d6 * 2;
          int offsetSrc = (d6 + 1 * 2);
          Unpack_Bard3_HelperC_Copy (ref tab2, offsetSrc, offsetDst, var_4);
          tab2.WriteWord (d6 * 2, var_2);
          Unpack_Bard3_HelperC_Copy (ref tab4, offsetSrc, offsetDst, var_4);
          tab4.WriteWord (d6 * 2, d4);
          d4 += 2;
          d5++;
        }
      }

      // Loop3
      {
        int d4 = 0;
        for (; d4 < 627; ) {
          int d6 = tab4.ReadWord (d4 * 2);
          if (d6 >= 627) {
            tab3.WriteWord (d6 * 2, d4);
          }
          else {
            tab3.WriteWord ((d6 + 1) * 2, d4);
            tab3.WriteWord (d6 * 2, d4);
          }
          d4++;
        }
      }
    }

    public static void Unpack_Bard3_HelperC_Copy (ref byte [] data, int offsetSrc, int offsetDst, int length) {
      if (offsetSrc == offsetDst)
        return;
      if (offsetDst > offsetSrc) {
        offsetSrc += length;
        offsetDst += length;
        for (int i = 0; i < length; i++) {
          offsetSrc--;
          offsetDst--;
          data.WriteByte (offsetDst, data.ReadByte (offsetSrc));
        }
      }
      else {
        for (int i = 0; i < length; i++) {
          data.WriteByte (offsetDst, data.ReadByte (offsetSrc));
          offsetSrc++;
          offsetDst++;
        }
      }
    }

    public static void Unpack_Bard3_Init (ref byte [] tab2, ref byte [] tab3, ref byte [] tab4) {
      for (int i = 0; i < 314; i++) {
        int d0 = i * 2;
        tab2.WriteWord (d0, 1);
        int d1 = i + 627;
        tab4.WriteWord (d0, d1);
        d1 *= 2;
        tab3.WriteWord (d1, i);
      }

      for (int d4 = 0, d5 = 314; d5 <= 626; d4 += 2, d5++) {
        int d0 = d5 * 2;
        int d1 = d4 * 2;
        int d3 = tab2.ReadWord (d1);
        d1 += 2;
        d3 += tab2.ReadWord (d1);
        tab2.WriteWord (d0, d3);
        tab4.WriteWord (d0, d4);
        tab3.WriteWord (d1, d5);
        d1 -= 2;
        tab3.WriteWord (d1, d5);
      }

      tab2.WriteWord (0x4E6, 0xFFFF);
      tab3.WriteWord (0x4E4, 0x0000);
    }

    public static void UncryptXor_Bard3 (ref byte [] data) {
      for (int d5 = 4928, offset = 0; d5 < 19712; d5++, offset++)
        data [offset + 4928] ^= data [offset];
    }

    public static byte [] _hard1 =  {
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
      2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
      3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
      4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5,
      6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7,
      8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9,
      10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11,
      12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15,
      16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19,
      20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23,
      24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30, 30, 31, 31,
      32, 32, 33, 33, 34, 34, 35, 35, 36, 36, 37, 37, 38, 38, 39, 39,
      40, 40, 41, 41, 42, 42, 43, 43, 44, 44, 45, 45, 46, 46, 47, 47,
      48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63
    };

    public static byte [] _hard2 =  {
      3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
      3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
      4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
      4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
      4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
      5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
      5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
      5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
      5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
      6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
      6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
      6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
      7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
      7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
      7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
      8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
    };

    public static Picture_Amiga LoadBard_4 (string filename, int pictureIndex) {
      string filenamePic = "BTCS_Amiga/" + filename + ".pic";
      if (!File.Exists (filenamePic))
        return (null);

      byte [] dump = ExtensionByte.CreateFromFile (filenamePic);

      int offset = 0;
      int size = (int) dump.ReadLong (ref offset);
      int depth = dump.ReadWord (ref offset);
      int nbColors = (int) Math.Pow (2, Math.Min (5, depth));
      int nbColorsTemp = (int) Math.Pow (2, depth);
      //int [] palette = new int [nbColors];
      int [] paletteDump = new int [nbColorsTemp];
      for (int i = 0; i < nbColors; i++)
        paletteDump [i] = dump.ReadWord (ref offset);
      for (int i = nbColors; i < nbColorsTemp; i++)
        //paletteDump [i] = (((i * 452) % 0x0F) << 8) + (((i * 16186) % 0x0F) << 4) + (((i * 9955441) % 0x0F) << 0);
        paletteDump [i] = (((paletteDump [i - nbColors] & 0x0F00) >> 9) << 8) +
                          (((paletteDump [i - nbColors] & 0x00F0) >> 5) << 4) +
                          (((paletteDump [i - nbColors] & 0x000F) >> 1) << 0);
      //paletteDump [i] = 0xF0F;
      byte [] basePicture = new byte [1232 * depth];
      UnpackBitplanesRLE_Bard4 (dump, ref offset, basePicture, 14, 88, depth);
      //pic.WriteToFile ("monk.pic");
      Palette palette = new Palette (paletteDump, Palette.LookupTypeEnum.Amiga);
      Bitmap bitmapPicture = SpriteReader.CreateBitmap (basePicture, 0, 112, 88, 6, SpriteReader.ModesEnum.Planar, -1, palette, 0, SpriteReader.MasksEnum.Opaque);
      //bitmapPicture.Save (string.Format ("bard4 Amiga pics {0} 00.png", pictureIndex.ToString ().PadLeft (2, '0')), ImageFormat.Png);
      //palette.Bitmap.Save ("bard4 palette.png", ImageFormat.Png);

      // no anim

      string filenameScr = "BTCS_Amiga/" + filename + ".scr";
      bool noAnimsInPic = false;
      if (offset == dump.Length)
        noAnimsInPic = true;

      bool noAnimsInScr = false;
      SCR_anims scrAnims = null;
      try {
        scrAnims = new SCR_anims (filenameScr);
      }
      catch (FileNotFoundException) {
        noAnimsInScr = true;
      }

      if (noAnimsInPic && !noAnimsInScr)
        Console.WriteLine ("UNNEEDED SCR FILE");

      if (!noAnimsInPic && noAnimsInScr)
        throw new Exception ();

      if (noAnimsInPic)
        return (new Picture_Amiga (bitmapPicture, null));



      int animSize = (int) dump.ReadLong (ref offset);
      byte [] animDump = dump.Extract (offset, animSize);
      offset += animSize;

      if (offset != dump.Length)
        throw new Exception ();

      //animDump.WriteToFile ("bard4 anim RawData.dmp");

      int animDataOffset = 0;
      int nbAnims = animDump.ReadByte (ref animDataOffset); // 0x00
      //Console.WriteLine ();
      //Console.WriteLine ("[ANIMS]");
      //Console.WriteLine ("nbAnims = {0} (0x{0:X2})", nbAnims);

      int animFirstFrame = 0;
      // LoopAnims
      List<Bitmap> animsBitmaps = new List<Bitmap> ();
      animsBitmaps.Add (null); // dummy for base picture
      List<Point> animsPosXY = new List<Point> ();
      List<Size> animsSizeWH = new List<Size> ();
      List<Bitmap> animsBitmapBase = new List<Bitmap> ();

      for (int animIndex = 0; animIndex < nbAnims; animIndex++) {
        int a_animWidth_Bytes = animDump.ReadByte (ref animDataOffset); // 0x01
        int b_animHeight = animDump.ReadByte (ref animDataOffset); // 0x02
        int c_x = animDump.ReadByte (ref animDataOffset); // 0x03
        int d_y = animDump.ReadByte (ref animDataOffset); // 0x04
        int e_animNbFrames = animDump.ReadByte (ref animDataOffset); // 0x05
        //Console.WriteLine ();
        //Console.WriteLine ("[ANIM]");
        //Console.WriteLine ("a_animWidth_Bytes = {0} (0x{0:X2})", a_animWidth_Bytes);
        //Console.WriteLine ("b_animHeight      = {0} (0x{0:X2})", b_animHeight);
        //Console.WriteLine ("c_x               = {0} (0x{0:X2})", c_x);
        //Console.WriteLine ("d_y               = {0} (0x{0:X2})", d_y);
        //Console.WriteLine ("e_animNbFrames    = {0} (0x{0:X2})", e_animNbFrames);
        animsPosXY.Add (new Point (c_x * 8, d_y));
        animsSizeWH.Add (new Size (a_animWidth_Bytes * 8, b_animHeight));
        animsBitmapBase.Add (bitmapPicture.ExtractBitmap (c_x * 8, d_y, a_animWidth_Bytes * 8, b_animHeight));

        int a_animWidth_Bytes_STRIDE = a_animWidth_Bytes + a_animWidth_Bytes % 2;
        //if ((a_animWidth_Bytes % 2) != 0)
        //  throw new Exception ();

        int a0 = offset; // 0x06
        int baseFrameSize_STRIDE = a_animWidth_Bytes_STRIDE * b_animHeight;
        byte [] baseFrame = new byte [baseFrameSize_STRIDE];
        // LoopReadBaseFrame
        for (int i = 0; i < baseFrameSize_STRIDE; i++) {
          if (((i % a_animWidth_Bytes_STRIDE) == a_animWidth_Bytes) &&
               (a_animWidth_Bytes_STRIDE != a_animWidth_Bytes))
            baseFrame [i] = 0;
          else
            baseFrame [i] = (byte) animDump.ReadByte (ref animDataOffset);
        }
        //baseFrame.WriteToFile (string.Format ("bard4 anim {0} BaseFrame.dmp", animIndex.ToString ().PadLeft (2, '0')));

        int a3 = animDataOffset;
        int animLastFrame = animFirstFrame + e_animNbFrames;
        //Console.WriteLine ("[FRAMES]");
        // LoopFrames
        int frameIndex = 0;
        for (int animCurrentFrame = animFirstFrame; animCurrentFrame < animLastFrame; animCurrentFrame++, frameIndex++) {
          int g_opcode1 = animDump.ReadByte (ref a3);
          //Console.WriteLine ("[FRAME]");
          //Console.WriteLine ("g_opcode = {0} (0x{0:X2})", g_opcode1);

          int offsetBitplanX1232 = 0;

          // LoopBitplans
          List<byte []> bitplans = new List<byte []> ();
          for (int indexBitplan = 0; indexBitplan < 6; indexBitplan++) {
            animDataOffset = a3;

            byte [] bitplan = new byte [baseFrameSize_STRIDE];
            if ((g_opcode1 & (0x01 << indexBitplan)) == 0) {
              // TypeA : copy from base picture

              // LoopUnk05_a_widthBytes
              for (int animOffsetDeltaX_Bytes = 0; animOffsetDeltaX_Bytes < a_animWidth_Bytes; animOffsetDeltaX_Bytes++) {
                int pictureOffsetDeltaY = 0;
                int animOffsetDeltaY = 0;

                // LoopUnk06_b_height
                for (int animIndexY = 0; animIndexY < b_animHeight; animIndexY++) {
                  int animOffsetDeltaXY = animOffsetDeltaY + animOffsetDeltaX_Bytes;
                  int x = c_x;
                  int y = d_y;
                  int pictureAnimOffsetXY = y * 14 + x;
                  int pictureAnimOffsetDeltaXY = pictureOffsetDeltaY + animOffsetDeltaX_Bytes;

                  bitplan [animOffsetDeltaXY] = basePicture [offsetBitplanX1232 + pictureAnimOffsetXY + pictureAnimOffsetDeltaXY];

                  animOffsetDeltaY += a_animWidth_Bytes_STRIDE;
                  pictureOffsetDeltaY += 14;
                }
              }
            }
            else {
              // TypeB

              a3 = animDataOffset;
              // LoopUnk07_a_widthBytes_NextColumn
              //Console.WriteLine ("[COLUMNS]");
              for (int animOffsetDeltaX_Bytes = 0; animOffsetDeltaX_Bytes < a_animWidth_Bytes; animOffsetDeltaX_Bytes++) {
                // 5D7BE
                int h_nbColumnParts = animDump.ReadByte (ref a3);
                //Console.WriteLine ("[COLUMN]");
                //Console.WriteLine ("h_nbColumnParts = {0} (0x{0:X2})", h_nbColumnParts);

                int pictureOffsetDeltaY = 0;
                int animOffsetDeltaY = 0;

                // LoopUnk08_InsideColumn
                //Console.WriteLine ("[COLUMN_PARTS]");
                for (int columnPartIndex = 0; columnPartIndex < h_nbColumnParts; columnPartIndex++) {
                  // 5D7D8
                  int i_opcode2 = animDump.ReadByte (ref a3);
                  //Console.WriteLine ("[COLUMN_PART]");
                  //Console.WriteLine ("i_opcode2 = {0} (0x{0:X2})", i_opcode2);
                  animDataOffset = a3;
                  if (i_opcode2 < 88) {
                    // TypeBA : copy column from data to anim
                    //Console.WriteLine ("TypeBA : copy column from data to anim");

                    int nbTemp = i_opcode2 + 1;
                    // LoopUnk09_CopyColumn
                    for (int temp = 0; temp < nbTemp; temp++) {
                      // 5D7FE
                      int animOffsetDeltaXY = animOffsetDeltaY + animOffsetDeltaX_Bytes;
                      bitplan [animOffsetDeltaXY] = (byte) animDump.ReadByte (ref a3);
                      animOffsetDeltaY += a_animWidth_Bytes_STRIDE;
                      pictureOffsetDeltaY += 14;
                    }
                  }
                  else {
                    // 5D830
                    // TypeBB
                    a3 = animDataOffset;
                    if (i_opcode2 < 173) {
                      // TypeBBA : copy column from picture to anim
                      //Console.WriteLine ("TypeBBA : copy column from picture to anim");

                      int nbTemp = i_opcode2 - 85;
                      // LoopUnk10
                      for (int temp = 0; temp < nbTemp; temp++) {
                        int animOffsetDeltaXY = animOffsetDeltaY + animOffsetDeltaX_Bytes;
                        int x = c_x;
                        int y = d_y;
                        int pictureAnimOffsetXY = y * 14 + x;
                        int pictureAnimOffsetDeltaXY = pictureOffsetDeltaY + animOffsetDeltaX_Bytes;

                        bitplan [animOffsetDeltaXY] = basePicture [offsetBitplanX1232 + pictureAnimOffsetXY + pictureAnimOffsetDeltaXY];

                        animOffsetDeltaY += a_animWidth_Bytes_STRIDE;
                        pictureOffsetDeltaY += 14;
                      }
                    }
                    else {
                      // TypeBBB : init column with color
                      //Console.WriteLine ("TypeBBB : init column with color");

                      int nbTemp = i_opcode2 - 170;
                      byte color = (byte) animDump.ReadByte (ref a3);
                      // LoopUnk11
                      for (int temp = 0; temp < nbTemp; temp++) {
                        int animOffsetDeltaXY = animOffsetDeltaY + animOffsetDeltaX_Bytes;
                        bitplan [animOffsetDeltaXY] = color;
                        animOffsetDeltaY += a_animWidth_Bytes_STRIDE;
                        pictureOffsetDeltaY += 14;
                      }
                    }
                  }

                  // NextLoopUnk08_InsideColumn
                }

              }
            }

            // NextBitplan
            offsetBitplanX1232 += 1232;
            animDataOffset = a3;
            bitplans.Add (bitplan);
          } // LoopBitplans

          byte [] bitplansDump = new byte [baseFrameSize_STRIDE * 6];
          for (int bitplanIndex = 0; bitplanIndex < 6; bitplanIndex++) {
            byte [] bitplan = bitplans [bitplanIndex];
            bitplan.CopyTo (0, bitplansDump, baseFrameSize_STRIDE * bitplanIndex, baseFrameSize_STRIDE);
          }
          //bitplansDump.WriteToFile (string.Format ("bard4 anim {0} frame {1}.dmp", animIndex.ToString ().PadLeft (2, '0'), frameIndex.ToString ().PadLeft (2, '0')));
          Bitmap bitmapFrame = SpriteReader.CreateBitmap (bitplansDump, 0, a_animWidth_Bytes_STRIDE * 8, b_animHeight, 6, SpriteReader.ModesEnum.Planar, -1, palette, 0, SpriteReader.MasksEnum.Opaque);
          bitmapFrame = bitmapFrame.ExtractBitmap (0, 0, a_animWidth_Bytes * 8, b_animHeight);
          //bitmapFrame.Save (string.Format ("bard4 Amiga pics {0} {1} {2} {3}.png", pictureIndex.ToString ().PadLeft (2, '0'), animsBitmaps.Count.ToString ().PadLeft (2, '0'), animIndex.ToString ().PadLeft (2, '0'), frameIndex.ToString ().PadLeft (2, '0')), ImageFormat.Png);

          animsBitmaps.Add (bitmapFrame);
        } // LoopFrames
        animFirstFrame = animLastFrame;

      } // LoopAnims

      if (animDataOffset != animDump.Length)
        throw new Exception ();

      // SCR
      if (nbAnims != scrAnims._animsElements.Count)
        throw new Exception ();

      Anims_Amiga_Bard4 anims = new Anims_Amiga_Bard4 ();

      for (int animIndex = 0; animIndex < nbAnims; animIndex++) {
        List<SCR_element> animElements = scrAnims._animsElements [animIndex];
        //List<Bitmap> animFramesBitmap = animsFramesBitmap [animIndex];
        Point posXY = animsPosXY [animIndex];
        Size sizeWH = animsSizeWH [animIndex];
        Anim_Amiga_Bard4 anim = new Anim_Amiga_Bard4 (posXY);

        for (int frameIndex = 0; frameIndex < animElements.Count; frameIndex++) {
          SCR_element animElement = animElements [frameIndex];
          Bitmap animFrameBitmap = null;
          if (animElement._frame >= animsBitmaps.Count) {
            animFrameBitmap = null;
            //animFrameBitmap = new Bitmap (sizeWH.Width, sizeWH.Height);
            //Graphics g = Graphics.FromImage (animFrameBitmap);
            //g.Clear (Color.Red);
            Console.WriteLine ("ERROR IN ANIM");
          }
          else
            animFrameBitmap = animsBitmaps [animElement._frame];
          int delay = animElement._delay;
          Bitmap bitmap = null;
          if (animElement._frame == 0)
            bitmap = animsBitmapBase [animIndex];
          else
            bitmap = animFrameBitmap;

          AnimFrame_Amiga_Bard4 frame = new AnimFrame_Amiga_Bard4 (animIndex, frameIndex, delay, bitmap);
          anim.AddFrame (frame);
        }
        anims.AddAnim (anim);
      }

      return (new Picture_Amiga (bitmapPicture, anims));
    }

    public static void UnpackBitplanesRLE_Bard4 (byte [] src, ref int offsetSrc, byte [] dst, int widthBytes, int height, int depth) {
      int offsetDst = 0;
      int check = 0;
      for (int i = 0; i < depth; i++) {
        UnpackBitplaneRLE_Bard4 (src, ref offsetSrc, dst, ref offsetDst, widthBytes, height);
        check += height * widthBytes;
        if (check != offsetDst)
          throw new Exception ();
      }
    }

    public static void UnpackBitplaneRLE_Bard4 (byte [] src, ref int offsetSrc, byte [] dst, ref int offsetDst, int widthBytes, int height) {
      int size = height * widthBytes;
      int end = offsetDst + size;
      for (; ; ) {
        if (offsetDst == end)
          break;
        int b1 = src.ReadByte (ref offsetSrc);
        int b = b1;
        if (b1 < 0x80) {
          for (int i = 0; i <= b1; i++)
            dst.WriteByte (offsetDst++, src.ReadByte (ref offsetSrc));
        }
        else if (b1 < 0xC0) {
          b1 -= 0x7E;
          int from = offsetDst - widthBytes;
          for (int i = 0; i <= b1; i++)
            dst.WriteByte (offsetDst++, dst.ReadByte (ref from));
        }
        else {
          b1 -= 0xBD;
          int value = src.ReadByte (ref offsetSrc);
          for (int i = 0; i <= b1; i++)
            dst.WriteByte (offsetDst++, value);
        }
      }
    }

    public static byte [] Uncrypt1 (byte [] dump) {
      byte [] dst = new byte [1232 * 4];
      int bp0 = 1232 * 0;
      int bp1 = 1232 * 1;
      int bp2 = 1232 * 2;
      int bp3 = 1232 * 3;

      int index = 0;
      for (int byteIndexInPlan = 0; byteIndexInPlan < 1232; byteIndexInPlan++) {
        int v0 = 0;
        int v1 = 0;
        int v2 = 0;
        int v3 = 0;
        long lng = dump.ReadLong (index);
        index += 4;
        for (int bitIndexInByte = 0; bitIndexInByte < 8; bitIndexInByte++) {
          //v0 = (v0 & 0xFF) << 1;
          v0 <<= 1;
          v0 = (int) (((int) (lng & 0x01)) | (v0 & 0xFF));
          lng >>= 1;

          v1 <<= 1;
          v1 = (int) (((int) (lng & 0x01)) | (v1 & 0xFF));
          lng >>= 1;

          v2 <<= 1;
          v2 = (int) (((int) (lng & 0x01)) | (v2 & 0xFF));
          lng >>= 1;

          v3 <<= 1;
          v3 = (int) (((int) (lng & 0x01)) | (v3 & 0xFF));
          lng >>= 1;
        }
        dst.WriteByte (bp0 + byteIndexInPlan, v0);
        dst.WriteByte (bp1 + byteIndexInPlan, v1);
        dst.WriteByte (bp2 + byteIndexInPlan, v2);
        dst.WriteByte (bp3 + byteIndexInPlan, v3);
      }
      return (dst);
    }

    public static void Uncrypt2 (byte [] dump) {
      int index = 0;
      for (int bp = 0; bp < 4; bp++) {
        for (int d6 = 14; d6 < 1232; d6++) {
          int b1 = dump.ReadByte (index + 14);
          int b2 = dump.ReadByte (index);
          dump.WriteByte (index + 14, b1 ^ b2);
          index++;
        }
        index += 14;
      }
    }

    public static void CheckTree (Huffman huffman, byte [] dump, int start, int baseOffset) {
      for (int i = 0; i < 768; i++) {
        long a = huffman._tree.ReadLong (i * 10 + 0 + start);
        if (a == 0xFFFFFFFF)
          a = 0;
        else
          a += baseOffset;
        long b = huffman._tree.ReadLong (i * 10 + 4 + start);
        if (b == 0xFFFFFFFF)
          b = 0;
        else
          b += baseOffset;
        int c = huffman._tree.ReadByte (i * 10 + 8 + start);

        long aa = dump.ReadLong (i * 10 + 0);
        long bb = dump.ReadLong (i * 10 + 4);
        int cc;
        cc = dump.ReadByte (i * 10 + 8);

        if ((aa != 0) || (bb != 0))
          cc = 0;

        //if ((a == 0) && (b == 0) && (c == 0)) {
        //  if ((aa != 0) || (bb != 0))
        //    throw new Exception ();
        //  break;
        //}

        if ((a != aa) || (b != bb) || (c != cc))
          throw new Exception ();

        Console.WriteLine ("{6,4} {0:X8} {1:X8} {2:X2}    {3:X8} {4:X8} {5:X2}", a, b, c, aa, bb, cc, i);
      }
    }

    public Bitmap Bitmap {
      get {
        return (_bitmap);
      }
    }

    public List<IAnim> Anims {
      get {
        if (_anims == null)
          return (null);
        return (_anims.Anims);
      }
    }

    public static string [] _filenamesBard4 =
      {"orc", "gdwiz", "dragon", "drag2", "drag3", "dog", "evwar", "ogre", "ghost",
       "green", "troll", "girl", "giant", "grif", "lizard", "evwiz", "rat", "mino",
       "gnome", "guard", "goblin", "golem", "ass", "evconj", "skel", "spidey", "vamp",
       "barb", "treas", "gdconj", "war", "pal", "roge", "bard", "hunt", "monk", "mage",
       "arch", "still1", "still2", "still3", "still4", "still5", "still6", "still7" };

  }
}
